---
title: AddUserToGroup
summary: Adds an existing external-domain user to an existing group, gated by the IP allow-list.
status: draft
owner: "Not determined from source"
last_reviewed: 2026-09-22
---

# AddUserToGroup (OP-04)

## Request and response

| Part | Type | Description |
| --- | --- | --- |
| `inUserId` | `String` | The `SamAccountName` of the user to add to the group. |
| `inGroupName` | `String` | The `SamAccountName` of the target group. |

| Part | Type | Description |
| --- | --- | --- |
| `<return>` | `ADServiceResponse` | `Response` is `Success`, `UserDoesNotExist`, `GroupDoesNotExist`, `UserAlreadyInGroup`, or `Failure`. `Message` carries the caught exception text on a `Failure`, otherwise empty. |

## WebMethod attributes

`<WebMethod()>` — bare, no arguments supplied.

## Steps

1. `CheckAccess()`; on denial return `AccessDenied()` ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)).
2. Initialize the response sentinel to `Success`.
3. Open `Try`.
4. Call H2 `GetUserPrincipal` for the external domain.
   - If `Nothing`, set `Response = UserDoesNotExist` ([BR-16](../business-rules.md#br-16-a-group-membership-addition-requires-both-user-and-group-to-exist)).
5. Else call H3 `GetGroupPrincipal` for the external domain ([BR-33](../business-rules.md#br-33-a-group-is-resolved-by-samaccountname%2C-taking-the-first-match)).
   - If `Nothing`, set `Response = GroupDoesNotExist` ([BR-16](../business-rules.md#br-16-a-group-membership-addition-requires-both-user-and-group-to-exist)).
6. Else `groupPrin.Members.Add(userPrin)` followed by `groupPrin.Save()`.
7. `Catch ex As Exception`:
   - If `ex.Message.Contains("already exists")`, set `Response = UserAlreadyInGroup` ([BR-17](../business-rules.md#br-17-adding-an-already-a-member-user-is-a-distinct-outcome)).
   - Else set `Response = Failure`, `Message = ex.Message`.
8. `Finally` releases both principals.
9. Return the response sentinel.

## Process flow

```mermaid
flowchart TD
    A["Call AddUserToGroup"] --> B{"CheckAccess<br>HasAccess?"}
    B -->|"No"| C["Return AccessDenied"]
    B -->|"Yes"| D["GetUserPrincipal<br>external domain"]
    D -->|"Nothing"| E["Set UserDoesNotExist"]
    D -->|"Found"| F["GetGroupPrincipal<br>external domain"]
    F -->|"Nothing"| G["Set GroupDoesNotExist"]
    F -->|"Found"| H["Members.Add + Save"]
    H -.->|"Exception: already exists"| I["Set UserAlreadyInGroup"]
    H -.->|"Other exception"| J["Set Failure + ex.Message"]
    E --> K["Return response"]
    G --> K
    H --> K
    I --> K
    J --> K
```

## Shared state effects

None — this operation touches no process-wide state; all locals are per-request.

## Error handling

The operation always returns a sentinel `ADServiceResponse`; it never surfaces a framework SOAP fault. Duplicate-membership detection depends on an English substring match against the directory exception message, which is locale- and provider-fragile*. See [Faults](../faults.md) for the service-wide fault model.

## Authorization

Gated by [BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller): the caller must pass the IP allow-list check enforced by `CheckAccess`. See [Authentication and authorization](../authentication-authorization.md).

> **Note:** Statements marked with * were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts. Statements
> marked with ** could not be confirmed; treat as unknown until verified.

## Related documentation
- [Service index](../index.md)
- [Overview](../overview.md)
- [Architecture](../architecture.md)
- [Activation and hosting](../activation-hosting.md)
- [Operations](../operations.md)
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Authentication and authorization](../authentication-authorization.md)
- [Configuration](../configuration.md)
- [Dependencies](../dependencies.md)
- [Faults](../faults.md)
- [Operational runbook](../operational-runbook.md)
- [Tech debt](../tech-debt.md)

## Sources
- `src\ADUserServices\clsADUserServices.asmx.vb` (lines 228-267)
